{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cell_id": "00001-a00535fe-716d-487c-ae39-d3fc9a9714e5",
    "deepnote_cell_height": 408.04998779296875,
    "deepnote_cell_type": "code",
    "deepnote_output_heights": [
     null,
     20
    ],
    "deepnote_to_be_reexecuted": false,
    "execution_millis": 1413,
    "execution_start": 1665697791579,
    "id": "opM0QN-CPwry",
    "source_hash": "6f0d79be"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "from manipulation import running_as_notebook\n",
    "\n",
    "from pydrake.all import (\n",
    "    AddMultibodyPlantSceneGraph,\n",
    "    Box,\n",
    "    Parser,\n",
    "    DiagramBuilder,\n",
    "    MeshcatVisualizer,\n",
    "    MeshcatVisualizerParams,\n",
    "    MultibodyPlant,\n",
    "    Role,\n",
    "    BodyIndex,\n",
    "    RigidTransform,\n",
    "    RotationMatrix,\n",
    "    SceneGraph,\n",
    "    Simulator,\n",
    "    StartMeshcat,\n",
    "    RollPitchYaw,\n",
    ")\n",
    "\n",
    "from IPython.display import clear_output\n",
    "\n",
    "# Start the visualizer.\n",
    "meshcat = StartMeshcat()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cell_id": "d1ecc126-f4ff-4a01-95a4-644e1138b85c",
    "deepnote_cell_height": 69.26666259765625,
    "deepnote_cell_type": "markdown",
    "id": "l8Rm5122dy2R"
   },
   "source": [
    "## **Tuning MultibodyPlant for Simulation**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cell_id": "00002-f029c1d2-78a1-481d-be3a-c471b90256fe",
    "deepnote_cell_height": 428.066650390625,
    "deepnote_cell_type": "markdown",
    "tags": []
   },
   "source": [
    "# Problem Description\n",
    "\n",
    "In earlier homework problems, you were given the simulation environment to start with. Now, it is your turn to design a simulation! Simulations are incredibly important in robotic software development. The more accurate we can make our simulation, the better our controller can be and so on. In lecture, we learned about contact modeling and what the important parameters for a stable simulation are. In this exercise, you will investigate the underlying simulation details and get prepared for your project. Specifically, we will learn both how to reproduce desired simulation output and pinpoint simulation issues, in a classic physics example of two blocks stacking on a slope.\n",
    "\n",
    "**These are the learning goals of this exercise:**\n",
    "1. Learning to debug and engineer collision geometries.\n",
    "2. Understand the relative importance of simulation parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cell_id": "00003-13e26566-a101-4299-8797-ea67db5c4450",
    "deepnote_cell_height": 201.26666259765625,
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": false,
    "execution_millis": 1,
    "execution_start": 1665697792462,
    "source_hash": "58aad2ef",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Define size of blocks and the slope\n",
    "slope = 0.1\n",
    "q1 = [0.0, 0, 0.15]\n",
    "q2 = [0.0, 0, 0.20]\n",
    "\n",
    "# Block sizes\n",
    "block_size1 = [0.02, 0.02, 0.02] # Red block\n",
    "block_size2 = [0.02, 0.02, 0.02] # Green block"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cell_id": "00004-c722c325-eae1-4033-b3f0-bd505778106c",
    "deepnote_cell_height": 525.2666625976562,
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": false,
    "execution_millis": 39,
    "execution_start": 1665697792464,
    "source_hash": "5814eb27",
    "tags": []
   },
   "outputs": [],
   "source": [
    "#######################################################\n",
    "### Don't change/remove! For visualization purposes ###\n",
    "#######################################################\n",
    "\n",
    "nonstack_pose1 = [\n",
    "    [9.94885056e-01, -1.55900179e-02, 9.98031851e-02, 1.95741356e-02],\n",
    "    [1.56650268e-02, 9.99877295e-01, 3.21017596e-05, -1.15839339e-04],\n",
    "    [-9.97914393e-02, 1.53148200e-03, 9.95007198e-01, 5.83171660e-02],\n",
    "    [0.00000000e00, 0.00000000e00, 0.00000000e00, 1.00000000e00],\n",
    "]\n",
    "\n",
    "nonstack_pose2 = [\n",
    "    [-1.00114398e-01, 5.89905683e-03, 9.94958446e-01, 2.11092651e-01],\n",
    "    [4.44194866e-04, 9.99982590e-01, -5.88414908e-03, 2.24353720e-03],\n",
    "    [-9.94975834e-01, -1.47132606e-04, -1.00115275e-01, 3.89206165e-02],\n",
    "    [0.00000000e00, 0.00000000e00, 0.00000000e00, 1.00000000e00],\n",
    "]\n",
    "\n",
    "\n",
    "stack_pose1 = [\n",
    "    [9.94996362e-01, -3.77459725e-05, 9.99111553e-02, 2.64605688e-01],\n",
    "    [3.79942503e-05, 9.99999999e-01, -5.82200282e-07, -1.13024604e-06],\n",
    "    [-9.99111552e-02, 4.37533660e-06, 9.94996362e-01, 3.33522993e-02],\n",
    "    [0.00000000e00, 0.00000000e00, 0.00000000e00, 1.00000000e00],\n",
    "]\n",
    "\n",
    "stack_pose2 = [\n",
    "    [9.94996470e-01, -1.41453220e-05, 9.99100858e-02, 2.66676737e-01],\n",
    "    [1.41925281e-05, 1.00000000e00, 2.38281298e-07, -9.14267754e-07],\n",
    "    [-9.99100858e-02, 1.18088765e-06, 9.94996470e-01, 5.22415534e-02],\n",
    "    [0.00000000e00, 0.00000000e00, 0.00000000e00, 1.00000000e00],\n",
    "]\n",
    "\n",
    "#######################################################"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cell_id": "00015-37133919-d71a-4d48-b6ab-a99649d53f3b",
    "deepnote_cell_height": 164.46665954589844,
    "deepnote_cell_type": "markdown",
    "tags": []
   },
   "source": [
    "#  Set up your MultibodyPlant\n",
    "In practice, you may have a pre-packaged simulation engine that is doing its job. However, to make a simulation performant, you need to tune the right physics parameters, e.g. mass and frictions, for your specific application. Run the code below to set up the simulation testing code.\n",
    "\n",
    "Note, we use [SDF (Simulation Description Format)](http://sdformat.org/spec) respresentations to construct objects and then load them directly into our simulation with MultibodyPlant. SDF files allow us to define an object or robot for simulation through an intuitive XML format. We can use them to specify custom geometry, friction, mass, and even create separate linkages / joints. Full documentation for this file format can be found [here](http://sdformat.org/spec). A similar XML file format exists called [URDF (Unified Robot Description Format)](http://wiki.ros.org/urdf/XML). You may find these resources useful for your final project."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ground_slope_SDF = f\"\"\"\n",
    "<?xml version=\"1.0\" ?>\n",
    "<sdf version=\"1.6\">\n",
    "<model name=\"ground\">\n",
    "    <static>true</static>\n",
    "    <link name=\"ground\">\n",
    "        <collision name=\"ground_collision\">\n",
    "            <pose>0 0 0 0 {slope} 0</pose>\n",
    "            <geometry>\n",
    "                <box>\n",
    "                    <size>10 10 0.1</size>\n",
    "                </box>\n",
    "            </geometry>\n",
    "            <surface>\n",
    "                <friction>\n",
    "                    <ode>\n",
    "                        <mu>0.085</mu>\n",
    "                        <mu2>0.085</mu2>\n",
    "                    </ode>\n",
    "                </friction>\n",
    "            </surface>\n",
    "        </collision>\n",
    "        <visual name=\"ground_visual\">\n",
    "            <pose>0 0 0 0 {slope} 0</pose>\n",
    "            <geometry>\n",
    "                <box>\n",
    "                    <size>10 10 0.1</size>\n",
    "                </box>\n",
    "            </geometry>\n",
    "            <material>\n",
    "                <diffuse>0.8 0.8 0.8 1.0</diffuse>\n",
    "            </material>\n",
    "        </visual>\n",
    "    </link>\n",
    "</model>\n",
    "</sdf>\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_block_sdf(shape, name, mass=1, mu=1, color=[0.5, 0.5, 0.9, 1.0]):\n",
    "    return f\"\"\"\n",
    "    <?xml version=\"1.0\" ?>\n",
    "    <sdf version=\"1.6\">\n",
    "    <model name=\"{name}\">\n",
    "        <static>false</static>\n",
    "        <link name=\"{name}\">\n",
    "            <collision name=\"block_collision\">\n",
    "                <geometry>\n",
    "                    <box>\n",
    "                        <size>{shape.width()} {shape.depth()} {shape.height()}</size>\n",
    "                    </box>\n",
    "                </geometry>\n",
    "                <surface>\n",
    "                    <friction>\n",
    "                        <ode>\n",
    "                            <mu>{mu}</mu>\n",
    "                            <mu2>{mu}</mu2>\n",
    "                        </ode>\n",
    "                    </friction>\n",
    "                </surface>\n",
    "            </collision>\n",
    "            <visual name=\"block_visual\">\n",
    "                <geometry>\n",
    "                    <box>\n",
    "                        <size>{shape.width()} {shape.depth()} {shape.height()}</size>\n",
    "                    </box>\n",
    "                </geometry>\n",
    "                <material>\n",
    "                    <diffuse>{color[0]} {color[1]} {color[2]} {color[3]}</diffuse>\n",
    "                </material>\n",
    "            </visual>\n",
    "            <inertial>\n",
    "                <mass>{mass}</mass>\n",
    "                <inertia>\n",
    "                    <ixx>{1/12 * (shape.depth()**2 + shape.height()**2)}</ixx>\n",
    "                    <iyy>{1/12 * (shape.width()**2 + shape.height()**2)}</iyy>\n",
    "                    <izz>{1/12 * (shape.depth()**2 + shape.width()**2)}</izz>\n",
    "                </inertia>\n",
    "            </inertial>\n",
    "        </link>\n",
    "    </model>\n",
    "    </sdf>\n",
    "    \"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def MakeSimulation(time_step, mass, mu, simulation_time=1.0):\n",
    "    \"\"\"\n",
    "    Create simple simulation with blocks and a slope based on input parameters\n",
    "    In this exercise, we will tune these parameters to achieve desired simulation results\n",
    "    \"\"\"    \n",
    "    builder = DiagramBuilder()\n",
    "    plant, scene_graph = AddMultibodyPlantSceneGraph(\n",
    "        builder, time_step=time_step\n",
    "    )\n",
    "\n",
    "    # Add slope to the MultibodyPlant using SDF\n",
    "    parser = Parser(plant)\n",
    "    instance = parser.AddModelsFromString(ground_slope_SDF, \".sdf\")\n",
    "\n",
    "    # Add blocks to the MultibodyPlant using generated SDF\n",
    "    block1_sdf = generate_block_sdf(\n",
    "        Box(*block_size1),\n",
    "        \"block1\",\n",
    "        mass[0],\n",
    "        mu[0],\n",
    "        color=[0.8, 0, 0, 1.0],\n",
    "    )\n",
    "    instance = parser.AddModelsFromString(block1_sdf, \".sdf\")\n",
    "    block2_sdf = generate_block_sdf(\n",
    "        Box(*block_size2),\n",
    "        \"block2\",\n",
    "        mass[1],\n",
    "        mu[1],\n",
    "        color=[0, 0.8, 0, 1.0],\n",
    "    )\n",
    "    instance = parser.AddModelsFromString(block2_sdf, \".sdf\")\n",
    "\n",
    "    nonstacking = simulation_time == 1\n",
    "    if simulation_time == 1:\n",
    "        pose1, pose2 = nonstack_pose1, nonstack_pose2\n",
    "    else:\n",
    "        pose1, pose2 = stack_pose1, stack_pose2\n",
    "\n",
    "    # Visualize goal block positions for parameter tuning tasks\n",
    "    instance = plant.AddModelInstance(\"block1_goal\")\n",
    "    plant.RegisterVisualGeometry(\n",
    "        plant.world_body(),\n",
    "        RigidTransform(pose1),\n",
    "        Box(*block_size1),\n",
    "        \"block1_goal\",\n",
    "        [0.8, 0, 0, 0.3],\n",
    "    )\n",
    "    instance = plant.AddModelInstance(\"block2_goal\")\n",
    "    plant.RegisterVisualGeometry(\n",
    "        plant.world_body(),\n",
    "        RigidTransform(pose2),\n",
    "        Box(*block_size2),\n",
    "        \"block2_goal\",\n",
    "        [0, 0.8, 0, 0.3],\n",
    "    )\n",
    "\n",
    "    # Build the plant and meshcat\n",
    "    plant.Finalize()\n",
    "    meshcat.Delete()\n",
    "    meshcat.DeleteAddedControls()\n",
    "    meshcat_param = MeshcatVisualizerParams()\n",
    "\n",
    "    # kProximity for collision geometry and kIllustration for visual geometry\n",
    "    meshcat_param.role = Role.kIllustration\n",
    "    visualizer = MeshcatVisualizer.AddToBuilder(\n",
    "        builder, scene_graph, meshcat, meshcat_param\n",
    "    )\n",
    "\n",
    "    meshcat.Set2dRenderMode(xmin=-0.2, xmax=0.2, ymin=-0.2, ymax=0.3)\n",
    "\n",
    "    # Initialize simulation\n",
    "    diagram = builder.Build()\n",
    "    simulator = Simulator(diagram)\n",
    "    if nonstacking:\n",
    "        simulator.set_target_realtime_rate(0.5)  # slow motion!\n",
    "    else:\n",
    "        simulator.set_target_realtime_rate(3)  # fast motion!\n",
    "\n",
    "    tf1 = RigidTransform(RollPitchYaw(0, 0, 0), q1)\n",
    "    tf2 = RigidTransform(RollPitchYaw(0, 0, 0), q2)\n",
    "    context = simulator.get_context()\n",
    "    plant_context = diagram.GetSubsystemContext(plant, context)\n",
    "    plant.SetFreeBodyPose(plant_context, plant.get_body(BodyIndex(2)), tf1)\n",
    "    plant.SetFreeBodyPose(plant_context, plant.get_body(BodyIndex(3)), tf2)\n",
    "\n",
    "    # Simulate and visualize\n",
    "    visualizer.StartRecording()\n",
    "    simulator.AdvanceTo(simulation_time)\n",
    "    visualizer.StopRecording()\n",
    "    visualizer.PublishRecording()\n",
    "\n",
    "    return simulator, diagram"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cell_id": "00019-5b12a63b-233d-4bd4-97af-6a1ec72de0c2",
    "deepnote_cell_height": 177.6666717529297,
    "deepnote_cell_type": "markdown",
    "id": "_4KTk534ex9e"
   },
   "source": [
    "(2pt) a. Run the code block below. We notice that the green block is falling past the red block and the thin slope. Why? Anwer in your written submission.\n",
    "\n",
    "(2pt) b. There are specific algorithms that can help avoid pass-through events in simulation, which we haven't implemented yet here. But even without them, we can address the issue by tuning the timesteps. Try tuning the simulation timestep in `set_sim_timestep` such that the both block falls on (but not inside) the slope. Should we decrease or increase the timestep if we want more accurate contact dynamics? Note that if the simulation advances too fast, you can pause and reset simulation to check the initial state.\n",
    "\n",
    "HINT: You should only change timestep value by factors of 10.\n",
    "\n",
    "(NOTE: If the simulation advances too fast, you can pause and reset simulation to check the initial state.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cell_id": "9ea215398a614ae18f978de029b49093",
    "deepnote_cell_height": 255.26666259765625,
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": true,
    "execution_millis": 1968,
    "execution_start": 1665697130970,
    "source_hash": "e051ad6f",
    "tags": []
   },
   "outputs": [],
   "source": [
    "def set_sim_timestep():\n",
    "    \"\"\"\n",
    "    Tune simulation timestep such that the blocks do not fall under the slope.\n",
    "    \"\"\"\n",
    "    time_step = 0.1 # MODIFY HERE\n",
    "    mass1, mass2 = 0.01, 5\n",
    "    return time_step, (mass1, mass2), (0.1, 0.1)\n",
    "\n",
    "simulator_b, diagram_b = MakeSimulation(*set_sim_timestep())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cell_id": "00024-9034edd5-5e1f-4e43-9ab2-9b23b456bcde",
    "deepnote_cell_height": 318.066650390625,
    "deepnote_cell_type": "markdown",
    "tags": []
   },
   "source": [
    "# Sliding blocks\n",
    "\n",
    "(2pt) c. Changing the mass of simulated objects will of course impact how the simulation behaves. In our simple simulation, we can imagine changing mass will most notably impact the magnitude of gravitational force on the objects. Let's try to reproduce the following gif by tuning the masses of the blocks in `set_block_masses`.\n",
    "\n",
    "HINT: You should only change mass values by factors of 10.\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/RussTedrake/manipulation/master/figures/exercises/sim_tuning_final_state1.gif\" width=\"700\"> "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cell_id": "425002d5f8e04ed2a61a6c28c37e6bc7",
    "deepnote_cell_height": 255.26666259765625,
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": true,
    "execution_millis": 1979,
    "execution_start": 1665697137065,
    "source_hash": "523447de",
    "tags": []
   },
   "outputs": [],
   "source": [
    "q1 = [0.0, 0, 0.065] # Initial pose of the red block\n",
    "q2 = [0.0, 0, 0.087] # Initial pose of the green block\n",
    "\n",
    "def set_block_masses():\n",
    "    \"\"\"\n",
    "    Tune block masses to match the gif above.\n",
    "    \"\"\"\n",
    "    mass1, mass2 = 1.0, 0.001 # MODIFY HERE\n",
    "    mu1, mu2 = 0.1, 0.1\n",
    "    return 0.001, (mass1, mass2), (mu1, mu2)\n",
    "\n",
    "simulator_c, diagram_c = MakeSimulation(*set_block_masses())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cell_id": "00026-5744237e-0606-41ec-94ef-789475c2d982",
    "deepnote_cell_height": 663.2666625976562,
    "deepnote_cell_type": "markdown",
    "tags": []
   },
   "source": [
    "# Stacking blocks\n",
    "(3pt) d. We define the slope angle as $\\alpha$ and the mass of each block to be $m_1$ (top block) and $m_2$ (bottom block). The friction between the blocks is defined by $\\mu_1$, and the friction between the slope surface and the bottom block is $\\mu_2$. \n",
    "\n",
    "Assume the two cube blocks are on top of each other on the slope. What constraint do we need on the friction parameters $\\mu_1,\\mu_2$ to satisfy such that the top block is sticking to the bottom block (no relative velocity) and the bottom block is sliding down the slope at a constant speed? Which coefficient must be greater and do they depend on the mass $m_1,m_2$ of the blocks? Draw out the free-body [diagram](https://youtu.be/N19SU7vgX7c?t=3394) to derive an answer. Write your answer in written submission."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(3pt) e. Changing the coefficient of friction between simulated object will impact how they behave in contact will each other. Try to reproduce the simulated result below by changing the friction values in `set_friction_coeffs`. \n",
    "\n",
    "HINT: You should only change values by factors of 10.\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/RussTedrake/manipulation/master/figures/exercises/sim_tuning_final_state2.gif\" width=\"700\"> "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cell_id": "4ebbf0e30d494b5886d0709155bec3f9",
    "deepnote_cell_height": 345.26666259765625,
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": true,
    "execution_millis": 1697,
    "execution_start": 1665697141066,
    "source_hash": "3b06269c",
    "tags": []
   },
   "outputs": [],
   "source": [
    "q1 = [0.0, 0, 0.065] # Initial pose of the red block\n",
    "q2 = [0.0, 0, 0.087] # Initial pose of the green block\n",
    "\n",
    "def set_friction_coeffs():\n",
    "    \"\"\"\n",
    "    Choose friction coefficients to make the blocks stack and move as shown above.\n",
    "    \"\"\"\n",
    "    time_step = 0.00001\n",
    "    mass1, mass2 = 0.1, 0.05\n",
    "    mu1, mu2 = 0.01, 0.1 # MODIFY HERE\n",
    "    return time_step, (mass1, mass2), (mu1, mu2)\n",
    "\n",
    "# We have to make simulator timestep really small, still the blocks slide relative to each other a little bit.\n",
    "# Why? Check the last section here https://drake.mit.edu/doxygen_cxx/group__contact__engineering.html\n",
    "simulator_e, diagram_e = MakeSimulation(\n",
    "    *set_friction_coeffs(), simulation_time=1.9\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cell_id": "00028-02a2f40b-4f60-41f6-981a-41c544fc214f",
    "deepnote_cell_height": 540.8333129882812,
    "deepnote_cell_type": "markdown",
    "id": "MwE8yNg58VQN"
   },
   "source": [
    "## How will this notebook be Graded?\n",
    "\n",
    "If you are enrolled in the class, this notebook will be graded using [Gradescope](www.gradescope.com). You should have gotten the enrollement code on our announcement in Piazza. \n",
    "\n",
    "For submission of this assignment, you must do two things. \n",
    "- Download and submit the notebook `simulation_tuning.ipynb` to Gradescope's notebook submission section, along with your notebook for the other problems.\n",
    "- Write down your answers to 5.6 to a separately pdf file and submit it to Gradescope's written submission section. \n",
    "\n",
    "We will evaluate the local functions in the notebook to see if the function behaves as we have expected. For this exercise, the rubric is as follows:\n",
    "- [2 pts] 5.6.a  Reason for block falling is answered correctly and attached to written submission. \n",
    "- [2 pts] 5.6.b `set_sim_timestep` is implemented correctly. \n",
    "- [2 pts] 5.6.c `set_block_masses` is implemented correctly.\n",
    "- [3 pts] 5.6.d  The analysis is answered correctly and attached to written submission.\n",
    "- [3 pts] 5.6.e `set_friction_coeffs` is implemented correctly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cell_id": "00029-963d5131-c568-408e-93b9-c630bc6474f2",
    "deepnote_cell_height": 254.53334045410156,
    "deepnote_cell_type": "code",
    "deepnote_to_be_reexecuted": true,
    "execution_millis": 825,
    "execution_start": 1665697197682,
    "id": "xj5nAh4g8VQO",
    "source_hash": "9571c437"
   },
   "outputs": [],
   "source": [
    "from manipulation.exercises.clutter.test_simulation_tuning import (\n",
    "    TestSimulationTuning,\n",
    ")\n",
    "from manipulation.exercises.grader import Grader\n",
    "Grader.grade_output([TestSimulationTuning], [locals()], \"results.json\")\n",
    "Grader.print_test_results(\"results.json\")"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "colab": {
   "collapsed_sections": [],
   "name": "analytic_antipodal_grasps.ipynb",
   "provenance": []
  },
  "deepnote": {},
  "deepnote_execution_queue": [
   {
    "cellId": "00011-796a6509-273e-432d-91b9-00df2428536c",
    "msgId": "1eed1b67-4ee0-4d28-a4fe-9c034885e64a",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "00014-1c2f9e1b-e605-44fe-a251-6a60c1617e54",
    "msgId": "f919ab91-d74a-4710-8e4e-c8309d163b82",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "aaca56bb2788409eb0d6326a5c7c93ae",
    "msgId": "2eca61db-3ccc-411b-94f7-a5ff99cc20bc",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "00016-a24fede7-7dde-491f-b54f-19ab3ccd9615",
    "msgId": "58d7d8fe-6957-4a8d-9a65-e9462fca5aaa",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "00017-9f7112c0-f631-4b15-a4e4-6703ce1afee9",
    "msgId": "bdb930e3-ebda-4590-aa38-4eb217485d25",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "00018-9438cff7-7e3e-45a4-af3d-b2ed74b15fec",
    "msgId": "f48b6f27-3e76-4ad3-aed6-2a5aacbe2143",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "9ea215398a614ae18f978de029b49093",
    "msgId": "d7c85bd2-93b0-429d-b39a-ad4455f4de16",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "00020-c2c7f813-3e0d-455e-9006-d3cb53d8b609",
    "msgId": "2761df7c-2111-4913-983b-a6d6851e7061",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "a5c54dfaa34145aba39e22b9e0f40346",
    "msgId": "aaad47d9-913c-4061-a03d-795138afef59",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "00022-278acda9-77b6-40c2-b476-ee216218b137",
    "msgId": "f9e9d8bd-51c0-42d5-8442-0c0069af7da4",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "00023-b6f28470-27f0-42b1-8012-4f53d3ffa863",
    "msgId": "8d757101-27ea-4ce8-b93e-297c1132e8ba",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "425002d5f8e04ed2a61a6c28c37e6bc7",
    "msgId": "d65947cc-d5c8-484d-8fdb-26a0db5ae7df",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "00025-855a9555-9786-4131-9427-ae611ba8fcab",
    "msgId": "e7a41807-a77b-468b-8d09-ceeb5c3cedc7",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "4ebbf0e30d494b5886d0709155bec3f9",
    "msgId": "6745ef5a-a0b6-4d2c-87ab-05733c7c1530",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "00027-5e0f441d-33f5-461d-8d2f-bdb5ee64ec9b",
    "msgId": "45587375-b5cd-4b12-8a8d-16b8c069570b",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "00029-963d5131-c568-408e-93b9-c630bc6474f2",
    "msgId": "c31675c5-7bf6-411d-89f2-99fa530ebf9b",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   },
   {
    "cellId": "00030-e6dbd700-0ee0-4c56-b1af-f1a81b4288a5",
    "msgId": "90081981-a580-421c-bd1c-891ad2ddc166",
    "sessionId": "3672865d-4588-445f-9770-f1de5b6fab18"
   }
  ],
  "deepnote_notebook_id": "6b55a0fd-8b2f-4d07-9a60-d1da6e62e925",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  },
  "vscode": {
   "interpreter": {
    "hash": "e7370f93d1d0cde622a1f8e1c04877d8463912d04d973331ad4851f04de6915a"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}